﻿$jo = function() {
    function getClass(object) {
        if (!arguments.length)
            return undefined;
        if (object === undefined)
            return "undefined";
        if (object === null)
            return "null";
        var type = Object.prototype.toString.call(object).slice(8, -1);
        if (type == "Number" && isNaN(object))
            return "NaN";
        return type;
    };

    var Name = {
        _SYMBOLS: "_$",
        _NUMBERS: "0123456789",
        _LETTERS: "abcdefg" + "hijklmn" + "opqrst" + "uvwxyz",
        _CAPITALS: "ABCDEFG" + "HIJKLMN" + "OPQRST" + "UVWXYZ",
        _KEYWORD: ["break", "case", "catch", "continue", "default", "delete", "do", "else", "finally", "for", "function",
            "if", "in", "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with"],
        _RESERVED: ["abstract", "boolean", "byte", "char", "class", "const", "debugger", "double", "enum", "export", "extends", "final", "float", "goto",
            "implements", "import", "int", "interface", "long", "native",
            "package", "private", "protected", "public", "short", "static", "super", "synchronized", "throws", "transient", "volatile"],
        isQualifiedName: function(string) {
            if (getClass(string) != "String" || string.length == 0)
                return false;
            if (this._NUMBERS.indexOf(string[0]) >= 0)
                return false;
            for (var each in string)
                if ((this._SYMBOLS + this._NUMBERS + this._LETTERS + this._CAPITALS).indexOf(string[each]) < 0)
                    return false;
            if (("|" + this._KEYWORD.join("|") + this._RESERVED.join("|") + "|").indexOf("|" + string + "|") >= 0)
                return false;
            return true;
        }
    };

    var Package = {
        _Package: function(packageName) {
            this.toString = function() {
                return packageName;
            };
        },
        isPackage: function(object) {
            return object instanceof this._Package;
        },
        define: function(packagePath) {
            packagePath = packagePath.split(".");
            var currentPackage = window;
            for (var each in packagePath) {
                each = packagePath[each];
                if (currentPackage[each] !== undefined) {
                    if (!this.isPackage(currentPackage[each]))
                        throw Error("[" + currentPackage[each] + "] has already defined not as a package!");
                }
                else {
                    if (!Name.isQualifiedName(each))
                        throw new Error("IllegalArgument[" + each + "]!");
                    currentPackage[each] = new this._Package(this.isPackage(currentPackage) ? (currentPackage + "." + each) : each);
                }
                currentPackage = currentPackage[each];
            }
            return currentPackage;
        }
    };

    var Class = {
        _defineClass: function(name, source, forPackage) {
            // validate
            if (!Name.isQualifiedName(name))
                throw new Error("IllegalArgument[" + name + "]");
            if (source === undefined)
                throw new Error("IllegalArgument[" + source + "]");
            if (forPackage && !Package.isPackage(forPackage))
                throw new Error("[" + forPackage + "] is not a package!");
            if (source.$constructor && source._constructor)
                throw new Error("Only one constructors is allowed!");
            var constructor = source.$constructor || source._constructor;
            if (constructor && getClass(constructor) != "Function")
                throw new Error("IllegalArgument[" + constructor + "]");

            // the virtual constructor
            var Class = (forPackage || window)[name] = function() { // TODO - no window by default?
                var callee = arguments.callee; // the function which is being called, maybe it is different from this
                for (var each in this.$Class.$source.$instance)
                    if (this.$Class.$source.$instance[each] == $abstract)
                        throw new Error("Abstract class [" + this.$Class.$name + "] can not be instantiated!");
                for (var each in this)
                    if (this[each] == $abstract)
                        throw new Error("Abstract method [" + each + "] must be implemented!");
                if (callee.$source.$super) // super constructor
                    callee.$source.$super.apply(this, arguments);
                if (callee.$source._constructor) { // private constructor
                    var calledByMyself = false;
                    for (var each in callee) {
                        if (callee.caller == callee[each]) {
                            calledByMyself = true;
                            break;
                        }
                    }
                    if (!calledByMyself)
                        throw new Error("Constructor [" + callee.$name + "] is not visible!");
                    callee.$source._constructor.apply(this, arguments);
                }
                else { // public constructor
                    if (!callee.$source.$constructor)
                        callee.$source.$constructor = function() {
                        };
                    callee.$source.$constructor.apply(this, arguments);
                }
            };

            // default toString method which can be overrided
            Class.toString = function() {
                return this.$name;
            };
            Class.prototype.toString = function() {
                return this.$Class.$name;
            };

            // compile
            // this static
            for (var each in source.$static)
                Class[each] = source.$static[each];
            // super static
            if (source.$super && source.$super.$source.$static)
                for (var each in source.$super.$source.$static)
                    if (Class[each] === undefined)
                        Class[each] = source.$super.$source.$static[each];
            // this instance
            for (var each in source.$instance)
                Class.prototype[each] = source.$instance[each];
            // super instance
            if (source.$super && source.$super.$source.$instance)
                for (var each in source.$super.$source.$instance)
                    if (Class.prototype[each] === undefined)
                        Class.prototype[each] = source.$super.$source.$instance[each];

            // basic informations
            Class.$name = forPackage ? (forPackage + "." + name) : name;
            Class.$source = source;
            Class.prototype.$Class = Class;
        },
        define: function(source) {
            var forPackage = source.$package ? Package.define(source.$package) : undefined;
            for (var each in source) {
                if (each === "$package")
                    continue;
                if (getClass(source[each]) != "Object")
                    throw new Error("IllegalArgument[" + each + "]!");
                this._defineClass(each, source[each], forPackage);
            }
        }
    };

    var $abstract = {
        $name: "$abstract",
        toString: function() {
            return $jo + "." + this.$name;
        }
    };

    var $jo = function() {
        Class.define.apply(Class, arguments);
    };
    $jo.$abstract = $abstract;
    $jo.toString = function() {
        return "$jo";
    }
    return $jo;
}();
